﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Fuse8.ContentManagementFramework.BizFacade.Services.Contents.Queryable
{
    public class WhereVisitor : ExpressionVisitor
    {
        readonly StringBuilder sb = new StringBuilder();
        private int parametersCount;
        private bool isCurrentVisitedExpressionParameter = true;
        private readonly List<object> parameters;

        public WhereVisitor(List<object> parameters)
        {
            this.parameters = parameters;
        }

        public string GetSqlQuery(Expression find)
        {
            Visit(find);

            return sb.ToString();
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression.NodeType == ExpressionType.Constant)
            {
                var constExpression = (ConstantExpression) node.Expression;
                SetConstant(constExpression, node.Member.Name);
                return node;
            }
            return base.VisitMember(node);
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            SetConstant(node);
            
            return base.VisitConstant(node);
        }

        protected void SetConstant(ConstantExpression exp, string name = null)
        {
            var val = exp.GetValueFromConst<object>(name);
            if (isCurrentVisitedExpressionParameter)
            {
                sb.Append(string.Format("@p{0}", parametersCount));
                parameters.Add(val);
                parametersCount++;
            }
            else
            {
                sb.Append(val);
            }
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            sb.Append("(");
            Visit(node.Left);
            switch (node.NodeType)
            {
                case ExpressionType.OrElse:
                case ExpressionType.Or:
                    sb.Append(" OR ");
                    break;
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    sb.Append(" AND ");
                    break;
            }
            Visit(node.Right);
            sb.Append(")");
            return node;            
        }
        
        protected override Expression VisitUnary(UnaryExpression node)
        {
            if (node.NodeType == ExpressionType.Not)
            {
                sb.Append("NOT ");
            }

            return base.VisitUnary(node);
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (GenericCmsEntityExtensions.SupportedMehtods.ContainsKey(node.Method.Name))
            {
                sb.Append("it.[");
                isCurrentVisitedExpressionParameter = false;
                Visit(node.Arguments[1]);
                isCurrentVisitedExpressionParameter = true;
                sb.Append("]");
                sb.Append(GenericCmsEntityExtensions.SupportedMehtods[node.Method.Name].Item1);                
                if (node.Arguments.Count > 2)
                {
                    sb.Append(GenericCmsEntityExtensions.SupportedMehtods[node.Method.Name].Item2);
                    Visit(node.Arguments[2]);
                    sb.Append(GenericCmsEntityExtensions.SupportedMehtods[node.Method.Name].Item3);
                }
                
            }
            else
            {
                throw new NotSupportedException(string.Format("Method {0} is not supported", node.Method.Name));
            }
            return node;           
        }         
    }
}